{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/klavis-ai/klavis/blob/main/examples/together-ai/Salesforce_Gmail_TogetherAI_Integration.ipynb)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "# Together AI + Klavis Salesforce + Gmail Integration\n",
    "\n",
    "# <img src=\"../../static/togetherai-klavis.png\" width=\"700\">\n",
    "\n",
    "This tutorial demonstrates how to build a powerful AI workflow using Together AI and Klavis MCP servers to:\n",
    "\n",
    "- **Salesforce Integration**: Automatically find CRM data like opportunities\n",
    "- **Gmail Integration**: Draft and send professional follow-up emails\n",
    "- **AI-Powered**: Use Together AI's LLMs for intelligent email composition\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "## Prerequisites\n",
    "\n",
    "Before we begin, you'll need:\n",
    "\n",
    "- **Together AI API key** - Get yours at [together.ai](https://together.ai/)\n",
    "- **Klavis AI API key** - Get yours at [klavis.ai](https://klavis.ai/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "# Install the required packages\n",
    "%pip install -qU together klavis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import json\n",
    "import webbrowser\n",
    "from together import Together\n",
    "from klavis import Klavis\n",
    "from klavis.types import McpServerName, ToolFormat\n",
    "\n",
    "# Set environment variables\n",
    "os.environ[\"TOGETHER_API_KEY\"] = \"YOUR_TOGETHER_API_KEY\"  # Replace with your actual Together API key\n",
    "os.environ[\"KLAVIS_API_KEY\"] = \"YOUR_KLAVIS_API_KEY\"      # Replace with your actual Klavis API key"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "## Step 1: Initialize Clients and Create MCP Server Instances\n",
    "\n",
    "First, let's set up our Together AI and Klavis clients, then create Salesforce and Gmail MCP server instances."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initialize clients\n",
    "together_client = Together(api_key=os.getenv(\"TOGETHER_API_KEY\"))\n",
    "klavis_client = Klavis(api_key=os.getenv(\"KLAVIS_API_KEY\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\ud83d\udd10 Opening OAuth authorization for Salesforce...\n"
     ]
    }
   ],
   "source": [
    "# Create Salesforce MCP Server instance\n",
    "salesforce_mcp_instance = klavis_client.mcp_server.create_server_instance(\n",
    "    server_name=McpServerName.SALESFORCE,\n",
    "    user_id=\"1234\", \n",
    ")\n",
    "\n",
    "# Open OAuth URL for Salesforce authorization\n",
    "webbrowser.open(salesforce_mcp_instance.oauth_url)\n",
    "print(f\"\ud83d\udd10 Opening OAuth authorization for Salesforce...\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\ud83d\udd10 Opening OAuth authorization for Gmail...\n"
     ]
    }
   ],
   "source": [
    "# Create Gmail MCP Server instance\n",
    "gmail_mcp_instance = klavis_client.mcp_server.create_server_instance(\n",
    "    server_name=McpServerName.GMAIL,\n",
    "    user_id=\"1234\",\n",
    ")\n",
    "\n",
    "# Open OAuth URL for Gmail authorization\n",
    "webbrowser.open(gmail_mcp_instance.oauth_url)\n",
    "print(f\"\ud83d\udd10 Opening OAuth authorization for Gmail...\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "## Step 2: Create workflow \n",
    "\n",
    "Now we'll create a workflow that can work with both Salesforce and Gmail MCP servers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\ud83d\udee0\ufe0f Calling tool: salesforce_get_opportunities with args: {'fields': [], 'limit': 50}\n",
      "\u2705 Tool salesforce_get_opportunities executed successfully\n",
      "\ud83d\udee0\ufe0f Calling tool: salesforce_update_opportunity with args: {'next_step': 'Meeting schedule on 07/31/2025', 'opportunity_id': '006fJ0000080dpaQAA'}\n",
      "\u2705 Tool salesforce_update_opportunity executed successfully\n",
      "\ud83d\udee0\ufe0f Calling tool: gmail_draft_email with args: {'body': 'Next step: Meeting schedule on 07/31/2025', 'subject': 'Follow-up on Together AI Opportunity', 'to': ['together.ai@example.com']}\n",
      "\u2705 Tool gmail_draft_email executed successfully\n",
      "\u2705 Task completed in 6 iterations\n",
      "A follow-up email for the next step has been drafted in your Gmail account with the subject \"Follow-up on Together AI Opportunity\" and the body \"Next step: Meeting schedule on 07/31/2025\".\n",
      "\n",
      "Done\n"
     ]
    }
   ],
   "source": [
    "class Workflow:\n",
    "    def __init__(self, together_client, klavis_client, mcp_server_urls, model=\"meta-llama/Llama-3.3-70B-Instruct-Turbo\"):\n",
    "        self.together = together_client\n",
    "        self.klavis = klavis_client\n",
    "        self.mcp_server_urls = mcp_server_urls\n",
    "        self.model = model\n",
    "    \n",
    "    def process_request(self, user_message):\n",
    "        # 1. Get available tools from all MCP servers and create tool-to-server mapping\n",
    "        all_tools = []\n",
    "        tool_to_server = {}  # Maps tool names to their server URLs\n",
    "        \n",
    "        for server_url in self.mcp_server_urls:\n",
    "            mcp_tools = self.klavis.mcp_server.list_tools(\n",
    "                server_url=server_url,\n",
    "                format=ToolFormat.OPENAI,\n",
    "            )\n",
    "            all_tools.extend(mcp_tools.tools)\n",
    "            \n",
    "            for tool in mcp_tools.tools:\n",
    "                tool_to_server[tool[\"function\"][\"name\"]] = server_url\n",
    "        \n",
    "        # 2. Initialize conversation\n",
    "        messages = [\n",
    "            {\"role\": \"system\", \"content\": \"You are a helpful AI assistant with access to tools. Complete requested tasks step by step with the tools available. When all tasks are completed, end your response with 'Done'.\"},\n",
    "            {\"role\": \"user\", \"content\": user_message}\n",
    "        ]\n",
    "        \n",
    "        max_iterations = 10 \n",
    "        iteration = 0\n",
    "        \n",
    "        # 3. Keep processing until no more tool calls are needed\n",
    "        while iteration < max_iterations:\n",
    "            iteration += 1\n",
    "            \n",
    "            # Call LLM with all available tools\n",
    "            response = self.together.chat.completions.create(\n",
    "                model=self.model,\n",
    "                messages=messages,\n",
    "                tools=all_tools\n",
    "            )\n",
    "            \n",
    "            assistant_message = response.choices[0].message\n",
    "            messages.append(assistant_message)\n",
    "                        \n",
    "            # If tool calls are needed\n",
    "            if assistant_message.tool_calls:\n",
    "                \n",
    "                # Execute tool calls\n",
    "                for tool_call in assistant_message.tool_calls:\n",
    "                    tool_name = tool_call.function.name\n",
    "                    tool_args = json.loads(tool_call.function.arguments)\n",
    "                    \n",
    "                    print(f\"\ud83d\udee0\ufe0f Calling tool: {tool_name} with args: {tool_args}\")\n",
    "                    \n",
    "                    # Find the correct server for this tool\n",
    "                    if tool_name in tool_to_server:\n",
    "                        server_url = tool_to_server[tool_name]\n",
    "                        \n",
    "                        try:\n",
    "                            tool_result = self.klavis.mcp_server.call_tools(\n",
    "                                server_url=server_url,\n",
    "                                tool_name=tool_name,\n",
    "                                tool_args=tool_args,\n",
    "                            )\n",
    "                            print(f\"\u2705 Tool {tool_name} executed successfully\")\n",
    "                        except Exception as e:\n",
    "                            tool_result = f\"Error executing tool {tool_name}: {str(e)}\"\n",
    "                            print(f\"\u274c Tool {tool_name} failed: {str(e)}\")\n",
    "                    else:\n",
    "                        tool_result = f\"Error: Tool {tool_name} not found in any server\"\n",
    "                        print(f\"\u274c Tool {tool_name} not found in any server\")\n",
    "                    \n",
    "                    messages.append({\n",
    "                        \"role\": \"tool\",\n",
    "                        \"tool_call_id\": tool_call.id,\n",
    "                        \"content\": str(tool_result)\n",
    "                    })\n",
    "                \n",
    "                # Continue the loop to see if LLM wants to make more tool calls\n",
    "                continue\n",
    "            \n",
    "            else:\n",
    "                # Check if the assistant said \"Done\" indicating all tasks are complete\n",
    "                content = assistant_message.content or \"\"\n",
    "                \n",
    "                if \"done\" in content.lower():\n",
    "                    print(f\"\u2705 Task completed in {iteration} iterations\")\n",
    "                    return assistant_message.content\n",
    "                else:\n",
    "                    continue\n",
    "        \n",
    "        # If we hit max iterations, return the last response\n",
    "        print(f\"\u26a0\ufe0f Reached max iterations ({max_iterations})\")\n",
    "        return assistant_message.content if assistant_message.content else \"Task completed but reached iteration limit\"\n",
    "\n",
    "workflow = Workflow(\n",
    "    together_client=together_client,\n",
    "    klavis_client=klavis_client,\n",
    "    mcp_server_urls=[salesforce_mcp_instance.server_url, gmail_mcp_instance.server_url],\n",
    "    model=\"meta-llama/Llama-3.3-70B-Instruct-Turbo\"\n",
    ")\n",
    "\n",
    "# Single request that uses both services\n",
    "multi_response = workflow.process_request(\n",
    "    \"\"\"\n",
    "    1. list my salesforce opportunities\n",
    "    2. then update Together AI opportunity's next step to meeting schedule on 07/31/2025\n",
    "    3. then in my gmail, draft a follow-up email for this next step\n",
    "    \"\"\"\n",
    ")\n",
    "\n",
    "print(multi_response)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "## Summary\n",
    "\n",
    "This tutorial demonstrated how to create a powerful Salesforce + Gmail integration using Together AI and Klavis MCP servers.\n",
    "\n",
    "### \ud83d\ude80 **Key Features:**\n",
    "\n",
    "- **Simple Workflow Classes**: Easy-to-use Workflow classes that work with any MCP server\n",
    "- **Flexible Model Selection**: Support for various Together AI models (Llama, Qwen, etc.)\n",
    "- **Real-time Execution**: Direct tool execution through Klavis API\n",
    "\n",
    "\n",
    "### \ud83d\udd27 **Next Steps:**\n",
    "\n",
    "- **Try More MCP Servers**: Integrate additional MCP servers like Slack, Notion, or Linear\n",
    "- **Custom Workflows**: Create more sophisticated multi-step workflows\n",
    "- **Error Handling**: Add robust error handling and retry logic\n",
    "- **Production Deployment**: Scale for production use with proper monitoring\n",
    "\n",
    "**Happy building with Together AI and Klavis!** \ud83d\ude80\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}